{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "mixed_representation.ipynb",
      "provenance": [],
      "collapsed_sections": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "v4NEw4EuXEun",
        "colab_type": "text"
      },
      "source": [
        "## Mixed Input Representations Design Pattern\n",
        "\n",
        "This design patterns refers to both:\n",
        "* Combining different types of data, like images + tabular metadata\n",
        "* Representing the same input in multiple formats"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "SNJV607UZcG5",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "import numpy as np\n",
        "import pandas as pd\n",
        "import tensorflow as tf\n",
        "\n",
        "from tensorflow import keras\n",
        "from tensorflow.keras import Model\n",
        "from tensorflow.keras.layers import Dense, Embedding, Input, Flatten, Conv2D, MaxPooling2D\n",
        "\n",
        "from google.cloud import bigquery"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "h1oUFQkPYGGf",
        "colab_type": "text"
      },
      "source": [
        "### Combining text and tabular inputs\n",
        "\n",
        "To demonstrate this we'll use a toy example dataset of restaurant reviews, defined below. For demo purposes, we've only defined the training dataset here."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "OrfipmT3YJ5C",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "reviews_data = {\n",
        "    \"review_text\": [\"The food was great, but it took forever to get seated.\", \"The tacos were life changing.\", \"This food made me question the presence of my taste buds.\"],\n",
        "    \"meal_type\": [\"lunch\", \"dinner\", \"dinner\"],\n",
        "    \"meal_total\": [50, 75, 60],\n",
        "    \"rating\": [4, 5, 1]\n",
        "}"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FV67EAQnOcwZ",
        "colab_type": "text"
      },
      "source": [
        "Step 1: process `review_text` so it can be fed in as an embedding"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "DHJUFlvfaYjx",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 70
        },
        "outputId": "f3b7fe8f-eef7-47d8-fa1b-719c22440aa6"
      },
      "source": [
        "vocab_size = 50\n",
        "tokenize = keras.preprocessing.text.Tokenizer(num_words=vocab_size)\n",
        "tokenize.fit_on_texts(reviews_data['review_text'])\n",
        "\n",
        "reviews_train = tokenize.texts_to_sequences(reviews_data['review_text'])\n",
        "max_sequence_len = 20\n",
        "reviews_train = keras.preprocessing.sequence.pad_sequences(reviews_train, maxlen=max_sequence_len, padding='post')\n",
        "\n",
        "print(reviews_train)"
      ],
      "execution_count": 27,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[[ 1  2  3  4  5  6  7  8  9 10 11  0  0  0  0  0  0  0  0  0]\n",
            " [ 1 12 13 14 15  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0]\n",
            " [16  2 17 18 19  1 20 21 22 23 24  0  0  0  0  0  0  0  0  0]]\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DBoPTo4VOiV8",
        "colab_type": "text"
      },
      "source": [
        "Step 2: convert `meal_type` to one-hot"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "LqdS68pKadJV",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "possible_meal_vocab = ['breakfast', 'lunch', 'dinner']\n",
        "one_hot_meals = []\n",
        "\n",
        "for i in reviews_data['meal_type']:\n",
        "  one_hot_arr = [0] * len(possible_meal_vocab)\n",
        "  one_index = possible_meal_vocab.index(i)\n",
        "  one_hot_arr[one_index] = 1\n",
        "  one_hot_meals.append(one_hot_arr)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SBnvDq7nOpfA",
        "colab_type": "text"
      },
      "source": [
        "Step 3: combine one-hot `meal_type` with `meal_total` into a single array"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "z94gXXgWahks",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "tabular_features = np.concatenate((np.array(one_hot_meals), np.expand_dims(reviews_data['meal_total'], axis=1)), axis=1)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ebDMuMLwOs1y",
        "colab_type": "text"
      },
      "source": [
        "Step 4: build the tabular and embedding layers with the Keras functional API"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "UbxUlIrBW9kw",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "batch_size = len(reviews_data['review_text'])\n",
        "\n",
        "embedding_input = Input(shape=(max_sequence_len,))\n",
        "embedding_layer = Embedding(batch_size, 64)(embedding_input)\n",
        "embedding_layer = Flatten()(embedding_layer)\n",
        "embedding_layer = Dense(3, activation='relu')(embedding_layer)\n",
        "\n",
        "tabular_input = Input(shape=(len(tabular_features[0]),))\n",
        "tabular_layer = Dense(32, activation='relu')(tabular_input)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VedDGp37ak_0",
        "colab_type": "text"
      },
      "source": [
        "Step 5: concatenate the layers into a model"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "846hSUp7Nd8v",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "merged_input = keras.layers.concatenate([embedding_layer, tabular_layer])\n",
        "merged_dense = Dense(16)(merged_input)\n",
        "output = Dense(1)(merged_dense)\n",
        "\n",
        "model = Model(inputs=[embedding_input, tabular_input], outputs=output)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "m7B1FDyJOP8Q",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 496
        },
        "outputId": "673f82e0-3563-4b89-c0de-b8de55a39d1f"
      },
      "source": [
        "# Preview the model architecture\n",
        "model.summary()"
      ],
      "execution_count": 32,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Model: \"model\"\n",
            "__________________________________________________________________________________________________\n",
            "Layer (type)                    Output Shape         Param #     Connected to                     \n",
            "==================================================================================================\n",
            "input_5 (InputLayer)            [(None, 20)]         0                                            \n",
            "__________________________________________________________________________________________________\n",
            "embedding_3 (Embedding)         (None, 20, 64)       192         input_5[0][0]                    \n",
            "__________________________________________________________________________________________________\n",
            "flatten_3 (Flatten)             (None, 1280)         0           embedding_3[0][0]                \n",
            "__________________________________________________________________________________________________\n",
            "input_6 (InputLayer)            [(None, 4)]          0                                            \n",
            "__________________________________________________________________________________________________\n",
            "dense_4 (Dense)                 (None, 3)            3843        flatten_3[0][0]                  \n",
            "__________________________________________________________________________________________________\n",
            "dense_5 (Dense)                 (None, 32)           160         input_6[0][0]                    \n",
            "__________________________________________________________________________________________________\n",
            "concatenate (Concatenate)       (None, 35)           0           dense_4[0][0]                    \n",
            "                                                                 dense_5[0][0]                    \n",
            "__________________________________________________________________________________________________\n",
            "dense_6 (Dense)                 (None, 16)           576         concatenate[0][0]                \n",
            "__________________________________________________________________________________________________\n",
            "dense_7 (Dense)                 (None, 1)            17          dense_6[0][0]                    \n",
            "==================================================================================================\n",
            "Total params: 4,788\n",
            "Trainable params: 4,788\n",
            "Non-trainable params: 0\n",
            "__________________________________________________________________________________________________\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lEVRtfdaa4E-",
        "colab_type": "text"
      },
      "source": [
        "### Tabular data multiple ways\n",
        "\n",
        "We'll build on the review dataset example in the previous section to show a quick demo of how you might represent rating as a feature in two different ways. "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b71FpIWabT6o",
        "colab_type": "text"
      },
      "source": [
        "Step 1: write a function to bucket ratings using a threshold"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ffXcpSyROUZ_",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def good_or_bad(rating):\n",
        "  if rating > 3:\n",
        "    return 1\n",
        "  else:\n",
        "    return 0"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2tyTkmoobcSN",
        "colab_type": "text"
      },
      "source": [
        "Step 2: Bucket the data and create a new input array with both the numeric and bucketed (boolean) rating"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "2x6i80qRbPy5",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "outputId": "4ac2a74e-de55-4c72-bbe8-0580ac29936c"
      },
      "source": [
        "rating_processed = []\n",
        "\n",
        "for i in reviews_data['rating']:\n",
        "  rating_processed.append([i, good_or_bad(i)])\n",
        "\n",
        "print(rating_processed)"
      ],
      "execution_count": 34,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[[4, 1], [5, 1], [1, 0]]\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0v_a8956boX2",
        "colab_type": "text"
      },
      "source": [
        "### Mixed text representations\n",
        "\n",
        "We'll use the Stack Overflow dataset in BigQuery to show how you can create a model with text represented as both Bag of Words and embeddings. To run the code in this section you'll need a [Google Cloud project](https://cloud.google.com/resource-manager/docs/creating-managing-projects)."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "E65NFDtYcD1Y",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# Authenticate to connect to BigQuery\n",
        "from google.colab import auth\n",
        "auth.authenticate_user()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YV4gVVhN1PLc",
        "colab_type": "text"
      },
      "source": [
        "Step 1: Get the data from BigQuery and save it to a Pandas DataFrame.\n",
        "\n",
        "Be sure to replace `your-project` below with the name of your Google Cloud project."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "y33Sz-xObRIq",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "%%bigquery df --project your-project\n",
        "SELECT\n",
        "  title,\n",
        "  answer_count,\n",
        "  REPLACE(tags, \"|\", \",\") as tags\n",
        "FROM\n",
        "  `bigquery-public-data.stackoverflow.posts_questions`\n",
        "WHERE\n",
        "  REGEXP_CONTAINS( tags, r\"(?:keras|matplotlib|pandas)\")\n",
        "LIMIT 1000"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "g91xTB87jO8V",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 197
        },
        "outputId": "949720c2-d91b-4739-a7d3-946f4a8d0b0e"
      },
      "source": [
        "# Preview the dataset, note that we'll use some of the\n",
        "df.head()"
      ],
      "execution_count": 49,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/html": [
              "<div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th>title</th>\n",
              "      <th>answer_count</th>\n",
              "      <th>tags</th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>0</th>\n",
              "      <td>how to plot multiple lines while reading x and...</td>\n",
              "      <td>1</td>\n",
              "      <td>python,matplotlib,plot</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>1</th>\n",
              "      <td>How to assign child objects to parent objects ...</td>\n",
              "      <td>1</td>\n",
              "      <td>python,for-loop,pandas,dataframe</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>2</th>\n",
              "      <td>python: join group size to member rows in data...</td>\n",
              "      <td>2</td>\n",
              "      <td>python,pandas</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>3</th>\n",
              "      <td>Groupby Sum ignoring few columns</td>\n",
              "      <td>1</td>\n",
              "      <td>python,pandas</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>4</th>\n",
              "      <td>Why can't I freeze_panes on the xlsxwriter obj...</td>\n",
              "      <td>1</td>\n",
              "      <td>python,pandas,xlsxwriter</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>"
            ],
            "text/plain": [
              "                                               title  ...                              tags\n",
              "0  how to plot multiple lines while reading x and...  ...            python,matplotlib,plot\n",
              "1  How to assign child objects to parent objects ...  ...  python,for-loop,pandas,dataframe\n",
              "2  python: join group size to member rows in data...  ...                     python,pandas\n",
              "3                   Groupby Sum ignoring few columns  ...                     python,pandas\n",
              "4  Why can't I freeze_panes on the xlsxwriter obj...  ...          python,pandas,xlsxwriter\n",
              "\n",
              "[5 rows x 3 columns]"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 49
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c3Ajk0USaQr8",
        "colab_type": "text"
      },
      "source": [
        "Step 2: Define the vocab size and max sequence length, and create an instance of the `Tokenizer` class, fitting it on our Stack Overflow data."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "BVXA_Mn81tOQ",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "stacko_vocab_size = 200\n",
        "stacko_sequence_len = 40\n",
        "\n",
        "# Create a tokenizer for this data\n",
        "stacko_tokenize = keras.preprocessing.text.Tokenizer(num_words=stacko_vocab_size)\n",
        "stacko_tokenize.fit_on_texts(df['title'].values)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ZKNIDKa_EVyC",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 372
        },
        "outputId": "8f3e02db-14f6-4213-8cb0-3c659f2490f6"
      },
      "source": [
        "# Preview the first 20 words in the Tokenizer's vocabulary\n",
        "list(stacko_tokenize.word_index.keys())[:20]"
      ],
      "execution_count": 39,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "['pandas',\n",
              " 'in',\n",
              " 'to',\n",
              " 'a',\n",
              " 'matplotlib',\n",
              " 'of',\n",
              " 'dataframe',\n",
              " 'python',\n",
              " 'with',\n",
              " 'how',\n",
              " 'and',\n",
              " 'on',\n",
              " 'the',\n",
              " 'using',\n",
              " 'data',\n",
              " 'column',\n",
              " 'plot',\n",
              " 'from',\n",
              " 'for',\n",
              " 'columns']"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 39
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ag9NiApyab6a",
        "colab_type": "text"
      },
      "source": [
        "Step 3: Convert the questions to sequences for the embedding representation"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "0ZSc6PWhEb4P",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "questions_train_embedding = stacko_tokenize.texts_to_sequences(df['title'].values)\n",
        "questions_train_embedding = keras.preprocessing.sequence.pad_sequences(questions_train_embedding, maxlen=stacko_sequence_len, padding='post')"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Y3taTumc3Nth",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 87
        },
        "outputId": "a3c63b47-e92f-45cf-e78c-8f9bc71b9a5a"
      },
      "source": [
        "# Preview the embedding representation with the actual input text\n",
        "print(df['title'].iloc[0])\n",
        "print(questions_train_embedding[0])"
      ],
      "execution_count": 41,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "how to plot multiple lines while reading x and y from files in a for loop?\n",
            "[ 10   3  17  23  74 112 176  44  11  63  18 126   2   4  19 127   0   0\n",
            "   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0\n",
            "   0   0   0   0]\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Cwy7Gwg-airB",
        "colab_type": "text"
      },
      "source": [
        "Step 4: Create the Bag of Words representation"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "2r7inAln3VLv",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 176
        },
        "outputId": "5a0d5bd6-68c1-4912-c9ac-7d24b28c1d54"
      },
      "source": [
        "questions_train_matrix = stacko_tokenize.texts_to_matrix(df['title'].values)\n",
        "print(questions_train_matrix[0])"
      ],
      "execution_count": 42,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[0. 0. 1. 1. 1. 0. 0. 0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 1. 1. 1. 0. 0. 0. 1.\n",
            " 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.\n",
            " 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.\n",
            " 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n",
            " 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.\n",
            " 0. 0. 0. 0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n",
            " 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n",
            " 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n",
            " 0. 0. 0. 0. 0. 0. 0. 0.]\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VqzTVtmhan4W",
        "colab_type": "text"
      },
      "source": [
        "Step 5: Create the embedding and BOW input layers"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "NKHzDK4-dT3g",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "batch_size = len(df) # Note: we're using the whole dataset as the batch size for demo purposes\n",
        "\n",
        "embedding_input = Input(shape=(stacko_sequence_len,))\n",
        "embedding_layer = Embedding(batch_size, 64)(embedding_input)\n",
        "embedding_layer = Flatten()(embedding_layer)\n",
        "embedding_layer = Dense(32, activation='relu')(embedding_layer)\n",
        "\n",
        "bow_input = Input(shape=(stacko_vocab_size,))\n",
        "bow_layer = Dense(32, activation='relu')(bow_input)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VACj0wFUavua",
        "colab_type": "text"
      },
      "source": [
        "Step 6: Create the model with the embedding and BOW layers"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "-bBpSFjCZiCI",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "merged_text_input = keras.layers.concatenate([embedding_layer, bow_layer])\n",
        "merged_dense_text = Dense(16)(merged_text_input)\n",
        "merged_output = Dense(1)(merged_dense_text)\n",
        "\n",
        "model = Model(inputs=[embedding_input, bow_input], outputs=merged_output)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "vxxAA03GaMMb",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 496
        },
        "outputId": "9d0a65ce-3025-4e46-ed6f-3cfa2b48062c"
      },
      "source": [
        "model.summary()"
      ],
      "execution_count": 45,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Model: \"model_1\"\n",
            "__________________________________________________________________________________________________\n",
            "Layer (type)                    Output Shape         Param #     Connected to                     \n",
            "==================================================================================================\n",
            "input_7 (InputLayer)            [(None, 40)]         0                                            \n",
            "__________________________________________________________________________________________________\n",
            "embedding_4 (Embedding)         (None, 40, 64)       64000       input_7[0][0]                    \n",
            "__________________________________________________________________________________________________\n",
            "flatten_4 (Flatten)             (None, 2560)         0           embedding_4[0][0]                \n",
            "__________________________________________________________________________________________________\n",
            "input_8 (InputLayer)            [(None, 200)]        0                                            \n",
            "__________________________________________________________________________________________________\n",
            "dense_8 (Dense)                 (None, 32)           81952       flatten_4[0][0]                  \n",
            "__________________________________________________________________________________________________\n",
            "dense_9 (Dense)                 (None, 32)           6432        input_8[0][0]                    \n",
            "__________________________________________________________________________________________________\n",
            "concatenate_1 (Concatenate)     (None, 64)           0           dense_8[0][0]                    \n",
            "                                                                 dense_9[0][0]                    \n",
            "__________________________________________________________________________________________________\n",
            "dense_10 (Dense)                (None, 16)           1040        concatenate_1[0][0]              \n",
            "__________________________________________________________________________________________________\n",
            "dense_11 (Dense)                (None, 1)            17          dense_10[0][0]                   \n",
            "==================================================================================================\n",
            "Total params: 153,441\n",
            "Trainable params: 153,441\n",
            "Non-trainable params: 0\n",
            "__________________________________________________________________________________________________\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Nldq8f7GbY_R",
        "colab_type": "text"
      },
      "source": [
        "### Extracting tabular features from text\n",
        "\n",
        "We'll create a new dataset of Stack Overflow questions, this time adding some tabular features extracted from the text and changing the prediction task to whether a question is answered.\n",
        "\n",
        "Step 1: Get the data in BigQuery. Remember to replace `your-project` below with the name of our GCP project."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "hDoQR46GbtUg",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "%%bigquery df_tabular --project your-project\n",
        "SELECT\n",
        "  title,\n",
        "  answer_count,\n",
        "  LENGTH(title) AS title_len,\n",
        "  ARRAY_LENGTH(SPLIT(title, \" \")) AS word_count,\n",
        "  ENDS_WITH(title, \"?\") AS ends_with_q_mark,\n",
        "  REPLACE(tags, \"|\", \",\") as tags,\n",
        "  IF\n",
        "    (answer_count > 0,\n",
        "      1,\n",
        "      0) AS is_answered\n",
        "FROM\n",
        "  `bigquery-public-data.stackoverflow.posts_questions`\n",
        "WHERE\n",
        "  REGEXP_CONTAINS( tags, r\"(?:keras|matplotlib|pandas)\")\n",
        "LIMIT 1000"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "MVbk1GmOcKcc",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 281
        },
        "outputId": "00f60549-0be0-4e40-84a4-662eacaf9ce2"
      },
      "source": [
        "df_tabular.head()"
      ],
      "execution_count": 52,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/html": [
              "<div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th>title</th>\n",
              "      <th>answer_count</th>\n",
              "      <th>title_len</th>\n",
              "      <th>word_count</th>\n",
              "      <th>ends_with_q_mark</th>\n",
              "      <th>tags</th>\n",
              "      <th>is_answered</th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>0</th>\n",
              "      <td>how to plot multiple lines while reading x and...</td>\n",
              "      <td>1</td>\n",
              "      <td>74</td>\n",
              "      <td>16</td>\n",
              "      <td>True</td>\n",
              "      <td>python,matplotlib,plot</td>\n",
              "      <td>1</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>1</th>\n",
              "      <td>How to assign child objects to parent objects ...</td>\n",
              "      <td>1</td>\n",
              "      <td>65</td>\n",
              "      <td>11</td>\n",
              "      <td>False</td>\n",
              "      <td>python,for-loop,pandas,dataframe</td>\n",
              "      <td>1</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>2</th>\n",
              "      <td>python: join group size to member rows in data...</td>\n",
              "      <td>2</td>\n",
              "      <td>51</td>\n",
              "      <td>9</td>\n",
              "      <td>False</td>\n",
              "      <td>python,pandas</td>\n",
              "      <td>1</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>3</th>\n",
              "      <td>Groupby Sum ignoring few columns</td>\n",
              "      <td>1</td>\n",
              "      <td>32</td>\n",
              "      <td>5</td>\n",
              "      <td>False</td>\n",
              "      <td>python,pandas</td>\n",
              "      <td>1</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>4</th>\n",
              "      <td>Why can't I freeze_panes on the xlsxwriter obj...</td>\n",
              "      <td>1</td>\n",
              "      <td>76</td>\n",
              "      <td>13</td>\n",
              "      <td>True</td>\n",
              "      <td>python,pandas,xlsxwriter</td>\n",
              "      <td>1</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>"
            ],
            "text/plain": [
              "                                               title  ...  is_answered\n",
              "0  how to plot multiple lines while reading x and...  ...            1\n",
              "1  How to assign child objects to parent objects ...  ...            1\n",
              "2  python: join group size to member rows in data...  ...            1\n",
              "3                   Groupby Sum ignoring few columns  ...            1\n",
              "4  Why can't I freeze_panes on the xlsxwriter obj...  ...            1\n",
              "\n",
              "[5 rows x 7 columns]"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 52
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Qz6j9kYjeGzQ",
        "colab_type": "text"
      },
      "source": [
        "Step 2: Extract the tabular features"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "6F64bm5eeJ1e",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 324
        },
        "outputId": "6361e046-3bb9-4d44-fcda-fac383fb218a"
      },
      "source": [
        "stacko_tabular_features = df_tabular[['title_len', 'word_count', 'ends_with_q_mark']]\n",
        "stacko_tabular_features['ends_with_q_mark'] = stacko_tabular_features['ends_with_q_mark'].astype(int)\n",
        "\n",
        "stacko_tabular_features.head()"
      ],
      "execution_count": 57,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:2: SettingWithCopyWarning: \n",
            "A value is trying to be set on a copy of a slice from a DataFrame.\n",
            "Try using .loc[row_indexer,col_indexer] = value instead\n",
            "\n",
            "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
            "  \n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/html": [
              "<div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th>title_len</th>\n",
              "      <th>word_count</th>\n",
              "      <th>ends_with_q_mark</th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>0</th>\n",
              "      <td>74</td>\n",
              "      <td>16</td>\n",
              "      <td>1</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>1</th>\n",
              "      <td>65</td>\n",
              "      <td>11</td>\n",
              "      <td>0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>2</th>\n",
              "      <td>51</td>\n",
              "      <td>9</td>\n",
              "      <td>0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>3</th>\n",
              "      <td>32</td>\n",
              "      <td>5</td>\n",
              "      <td>0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>4</th>\n",
              "      <td>76</td>\n",
              "      <td>13</td>\n",
              "      <td>1</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>"
            ],
            "text/plain": [
              "   title_len  word_count  ends_with_q_mark\n",
              "0         74          16                 1\n",
              "1         65          11                 0\n",
              "2         51           9                 0\n",
              "3         32           5                 0\n",
              "4         76          13                 1"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 57
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vnaB9XJVer-P",
        "colab_type": "text"
      },
      "source": [
        "Step 3: Create an `Input` layer for the tabular features"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "NXvbvN39ewZY",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "stacko_tabular_input = Input(shape=(len(stacko_tabular_features.values[0]),))\n",
        "stacko_tabular_layer = Dense(32, activation='relu')(stacko_tabular_input)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "02R-8BoFfETW",
        "colab_type": "text"
      },
      "source": [
        "Step 4: Define a model using `stacko_tabular_layer` and our BOW layer from above"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "q49LgjRDfLA9",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "merged_mixed_input = keras.layers.concatenate([stacko_tabular_layer, bow_layer])\n",
        "merged_mixed_text = Dense(16)(merged_mixed_input)\n",
        "merged_mixed_output = Dense(1)(merged_mixed_text)\n",
        "\n",
        "mixed_text_model = Model(inputs=[stacko_tabular_input, bow_input], outputs=merged_mixed_output)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "wLf8n00Ff6se",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 425
        },
        "outputId": "319e1226-86c9-43cf-8873-9323fe859eb6"
      },
      "source": [
        "mixed_text_model.summary()"
      ],
      "execution_count": 66,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Model: \"model_5\"\n",
            "__________________________________________________________________________________________________\n",
            "Layer (type)                    Output Shape         Param #     Connected to                     \n",
            "==================================================================================================\n",
            "input_11 (InputLayer)           [(None, 3)]          0                                            \n",
            "__________________________________________________________________________________________________\n",
            "input_8 (InputLayer)            [(None, 200)]        0                                            \n",
            "__________________________________________________________________________________________________\n",
            "dense_16 (Dense)                (None, 32)           128         input_11[0][0]                   \n",
            "__________________________________________________________________________________________________\n",
            "dense_9 (Dense)                 (None, 32)           6432        input_8[0][0]                    \n",
            "__________________________________________________________________________________________________\n",
            "concatenate_5 (Concatenate)     (None, 64)           0           dense_16[0][0]                   \n",
            "                                                                 dense_9[0][0]                    \n",
            "__________________________________________________________________________________________________\n",
            "dense_21 (Dense)                (None, 16)           1040        concatenate_5[0][0]              \n",
            "__________________________________________________________________________________________________\n",
            "dense_22 (Dense)                (None, 1)            17          dense_21[0][0]                   \n",
            "==================================================================================================\n",
            "Total params: 7,617\n",
            "Trainable params: 7,617\n",
            "Non-trainable params: 0\n",
            "__________________________________________________________________________________________________\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iyIvPEKvdUdR",
        "colab_type": "text"
      },
      "source": [
        "### Mixed image representations\n",
        "\n",
        "Note: there is no dataset used here, but the example below provides a demo of how you could create a model that merges both pixel value and tiled representations."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DIMxu46sa5bd",
        "colab_type": "text"
      },
      "source": [
        "Step 1: Define the pixel value and tiled representation layers"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "YiycXlR1e5hJ",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# Define image input layer (same shape for both pixel and tiled representation)\n",
        "image_input = Input(shape=(28,28,1))\n",
        "\n",
        "# Define pixel representation\n",
        "pixel_layer = Flatten()(image_input)\n",
        "\n",
        "# Define tiled representation\n",
        "tiled_layer = Conv2D(filters=16, kernel_size=3, activation='relu')(image_input)\n",
        "tiled_layer = MaxPooling2D()(tiled_layer)\n",
        "tiled_layer = tf.keras.layers.Flatten()(tiled_layer)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2f8EWe_la8l-",
        "colab_type": "text"
      },
      "source": [
        "Step 2: Concatenate the layers and create a model"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "4qRstAHofZAf",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "merged_image_layers = keras.layers.concatenate([pixel_layer, tiled_layer])\n",
        "\n",
        "merged_dense = Dense(16, activation='relu')(merged_image_layers)\n",
        "merged_output = Dense(1)(merged_dense)\n",
        "\n",
        "mixed_image_model = Model(inputs=image_input, outputs=merged_output)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "YzGp47SxfsCX",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 496
        },
        "outputId": "89f24287-4bf4-4e38-894f-981917c1d366"
      },
      "source": [
        "mixed_image_model.summary()"
      ],
      "execution_count": 70,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Model: \"model_6\"\n",
            "__________________________________________________________________________________________________\n",
            "Layer (type)                    Output Shape         Param #     Connected to                     \n",
            "==================================================================================================\n",
            "input_13 (InputLayer)           [(None, 28, 28, 1)]  0                                            \n",
            "__________________________________________________________________________________________________\n",
            "conv2d (Conv2D)                 (None, 26, 26, 16)   160         input_13[0][0]                   \n",
            "__________________________________________________________________________________________________\n",
            "input_12 (InputLayer)           [(None, 28, 28)]     0                                            \n",
            "__________________________________________________________________________________________________\n",
            "max_pooling2d (MaxPooling2D)    (None, 13, 13, 16)   0           conv2d[0][0]                     \n",
            "__________________________________________________________________________________________________\n",
            "flatten_5 (Flatten)             (None, 784)          0           input_12[0][0]                   \n",
            "__________________________________________________________________________________________________\n",
            "flatten_6 (Flatten)             (None, 2704)         0           max_pooling2d[0][0]              \n",
            "__________________________________________________________________________________________________\n",
            "concatenate_6 (Concatenate)     (None, 3488)         0           flatten_5[0][0]                  \n",
            "                                                                 flatten_6[0][0]                  \n",
            "__________________________________________________________________________________________________\n",
            "dense_23 (Dense)                (None, 16)           55824       concatenate_6[0][0]              \n",
            "__________________________________________________________________________________________________\n",
            "dense_24 (Dense)                (None, 1)            17          dense_23[0][0]                   \n",
            "==================================================================================================\n",
            "Total params: 56,001\n",
            "Trainable params: 56,001\n",
            "Non-trainable params: 0\n",
            "__________________________________________________________________________________________________\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab_type": "text",
        "id": "SFhjTV9fhkYa"
      },
      "outputs": [],
      "source": [
        "### Combining images and metadata\n",
        "\n",
        "This shows how we'd feed both images and associated metadata into a single model. To demonstrate this we'll be using the dummy dataset of tabular data below."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ByUKhqHvf_C7",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "tabular_image_metadata = {\n",
        "    'time': [9,10,2],\n",
        "    'visibility': [0.2, 0.5, 0.1],\n",
        "    'inclement_weather': [[0,0,1], [0,0,1], [1,0,0]],\n",
        "    'location': [[0,1,0,0,0], [0,0,0,1,0], [1,0,0,0,0]] \n",
        "}"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VxwWz-kIiE6s",
        "colab_type": "text"
      },
      "source": [
        "Step 1: Concatenate all tabular features"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "a__TEi_RhYXE",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "tabular_image_features = np.concatenate((\n",
        "    np.expand_dims(tabular_image_metadata['time'], axis=1),\n",
        "    np.expand_dims(tabular_image_metadata['visibility'], axis=1),    \n",
        "    np.array(tabular_image_metadata['inclement_weather']),\n",
        "    np.array(tabular_image_metadata['location'])\n",
        "), axis=1)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "q9zoZs2Ah0dy",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 70
        },
        "outputId": "f4920063-3f60-4759-c84e-1ca6ef6523a3"
      },
      "source": [
        "# Preview the data\n",
        "tabular_image_features"
      ],
      "execution_count": 82,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "array([[ 9. ,  0.2,  0. ,  0. ,  1. ,  0. ,  1. ,  0. ,  0. ,  0. ],\n",
              "       [10. ,  0.5,  0. ,  0. ,  1. ,  0. ,  0. ,  0. ,  1. ,  0. ],\n",
              "       [ 2. ,  0.1,  1. ,  0. ,  0. ,  1. ,  0. ,  0. ,  0. ,  0. ]])"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 82
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8ieRklxVg6Jv",
        "colab_type": "text"
      },
      "source": [
        "Step 2: Define the tabular layer"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "_cMbw3xJg5Rw",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "image_tabular_input = Input(shape=(len(tabular_image_features[0]),))\n",
        "image_tabular_layer = Dense(32, activation='relu')(image_tabular_input)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Imd8PtHxiLUJ",
        "colab_type": "text"
      },
      "source": [
        "Step 3: Merge the tabular layer with the tiled layer we defined above"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "afIIpkxFgMfg",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "mixed_image_layers = keras.layers.concatenate([image_tabular_layer, tiled_layer])\n",
        "\n",
        "merged_image_dense = Dense(16, activation='relu')(mixed_image_layers)\n",
        "merged_image_output = Dense(1)(merged_image_dense)\n",
        "\n",
        "mixed_image_tabular_model = Model(inputs=[image_tabular_input, tiled_input], outputs=merged_image_output)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "zU6MBG40h_rN",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 496
        },
        "outputId": "4dd0ebcb-f750-4d6f-8780-0db40ff105fa"
      },
      "source": [
        "mixed_image_tabular_model.summary()"
      ],
      "execution_count": 86,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Model: \"model_7\"\n",
            "__________________________________________________________________________________________________\n",
            "Layer (type)                    Output Shape         Param #     Connected to                     \n",
            "==================================================================================================\n",
            "input_13 (InputLayer)           [(None, 28, 28, 1)]  0                                            \n",
            "__________________________________________________________________________________________________\n",
            "conv2d (Conv2D)                 (None, 26, 26, 16)   160         input_13[0][0]                   \n",
            "__________________________________________________________________________________________________\n",
            "input_14 (InputLayer)           [(None, 10)]         0                                            \n",
            "__________________________________________________________________________________________________\n",
            "max_pooling2d (MaxPooling2D)    (None, 13, 13, 16)   0           conv2d[0][0]                     \n",
            "__________________________________________________________________________________________________\n",
            "dense_25 (Dense)                (None, 32)           352         input_14[0][0]                   \n",
            "__________________________________________________________________________________________________\n",
            "flatten_6 (Flatten)             (None, 2704)         0           max_pooling2d[0][0]              \n",
            "__________________________________________________________________________________________________\n",
            "concatenate_8 (Concatenate)     (None, 2736)         0           dense_25[0][0]                   \n",
            "                                                                 flatten_6[0][0]                  \n",
            "__________________________________________________________________________________________________\n",
            "dense_28 (Dense)                (None, 16)           43792       concatenate_8[0][0]              \n",
            "__________________________________________________________________________________________________\n",
            "dense_29 (Dense)                (None, 1)            17          dense_28[0][0]                   \n",
            "==================================================================================================\n",
            "Total params: 44,321\n",
            "Trainable params: 44,321\n",
            "Non-trainable params: 0\n",
            "__________________________________________________________________________________________________\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Copyright 2020 Google Inc. Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License"
      ]
    }
  ]
}